package clue.players2.ai;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Stack;

import org.javatuples.Triplet;

import clue.EnumerationEngine2;
import clue.board.Path;
import clue.board.Square;
import clue.cards.Card;
import clue.cards.RoomCard;
import clue.cards.SuspectCard;
import clue.cards.WeaponCard;
import clue.engine2.GameState2;
import clue.enums.Room;
import clue.enums.Suspect;
import clue.enums.Weapon;
import clue.players2.Player2;

public class MediumAIPlayer extends Player2 {
	private EnumerationEngine2 engine;
	private HashMap<Integer, Integer> IDToOpponentIdx;
	public int id;
	
	@Override
	public void gameStarted(GameState2 gs) {
		Card[] cards = new Card[gs.getOwnHand(this).size()];
		gs.getOwnHand(this).toArray(cards);
		engine = new EnumerationEngine2(gs.playerIDs.size(), cards);
		IDToOpponentIdx = new HashMap<>();
		int myID = gs.getOwnID(this);
		id = myID;
		List<Integer> ids = gs.playerIDs;
		int i;
		for (i = 0; i < ids.size(); i++) {
			if (ids.get(i) == myID)
				break;
		}
		int index = 0;
		for (int j = (i + 1) % ids.size(); j != i; j = (j + 1) % ids.size(), index++) {
			IDToOpponentIdx.put(ids.get(j), index);
		}
	}

	private int room;
	private int suspect;
	private int weapon;
	private Stack<Square> stack;
	private boolean moving;
	
	@Override
	public Path move(GameState2 gs, int dice) {
		if (moving) {
			Path p = new Path();
			int count = 0;
			while (!stack.isEmpty()) {
				if (count == dice)
					break;
				p.addSquare(stack.pop());
				count++;
			}
			if (stack.isEmpty())
				moving = false;
			return p;
		}
		int room = -1, suspect = -1, weapon = -1;
		double maxProb = -1;
		for (int r = 0; r < 9; r++) {
			if (Room.values()[r].equals(gs.IDToLocation.get(gs.getOwnID(this))))
				continue;
			for (int s = 0; s < 6; s++) {
				for (int w = 0; w < 6; w++) {
					if (engine.envProb[w][s][r] > maxProb) {
						maxProb = engine.envProb[w][s][r];
						room = r;
						suspect = s;
						weapon = w;
					}
				}
			}
		}
		
		if (room == -1) {
			for (int r = 0; r < 9; r++) {
				if (Room.values()[r].equals(gs.IDToLocation.get(gs.getOwnID(this))))
					continue;
				room = r;
				suspect = 0;
				weapon = 0;
				break;
			}
		}
		
		this.room = room;
		this.suspect = suspect;
		this.weapon = weapon;
		// find shortest path to
		//System.out.println("Finding shortest path to " + Room.values()[room]);
		Queue<Square> queue = new LinkedList<>();
		HashSet<Square> visited = new HashSet<>();
		HashMap<Square, Square> parent = new HashMap<>();
		queue.add(gs.IDToSquare.get(gs.getOwnID(this)));
		visited.add(gs.IDToSquare.get(gs.getOwnID(this)));
		while (!queue.isEmpty()) {
			Square s = queue.remove();
			//System.out.printf("(%d, %d) room = %s\n", s.x, s.y, s.room);
			if (Room.values()[room].equals(s.room)) {
				stack = new Stack<>();
				stack.push(s);
				while (parent.get(s) != null) {
					s = parent.get(s);
					//System.out.printf("(%d, %d) room = %s\n", s.x, s.y, s.room);
					stack.push(s);
				}
				break;
			}
			for (Square t : s.neighbors) {
				if (!visited.contains(t)) {
					if (t.room != null && !Room.values()[room].equals(t.room))
						continue;
					queue.add(t);
					parent.put(t, s);
					visited.add(t);
				}
			}
		}
		moving = true;
		Path p = new Path();
		int count = 0;
		stack.pop();
		while (!stack.isEmpty()) {
			if (count == dice)
				break;
			p.addSquare(stack.pop());
			count++;
		}
		if (stack.isEmpty())
			moving = false;
		return p;
	}

	@Override
	public Triplet<Room, Suspect, Weapon> suggest(GameState2 gs) {
		double maxProb = -1;
		int suspect = -1, weapon = -1;
		for (int s = 0; s < 6; s++) {
			for (int w = 0; w < 6; w++) {
				if (engine.envProb[w][s][room] > maxProb) {
					maxProb = engine.envProb[w][s][room];
					suspect = s;
					weapon = w;
				}
			}
		}
		if (suspect == -1) {
			suspect = this.suspect;
			weapon = this.weapon;
		}
		
		return new Triplet<Room, Suspect, Weapon>(gs.IDToLocation.get(gs.getOwnID(this)), Suspect.values()[suspect],Weapon.values()[weapon]);
	}

	@Override
	public Card refute(GameState2 gs, Integer suggestingPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		for (Card c : gs.getOwnHand(this)) {
			if (c instanceof RoomCard) {
				RoomCard rc = (RoomCard) c;
				if (rc.getRoom().equals(suggestion.getValue0()))
					return rc;
			} else if (c instanceof SuspectCard) {
				SuspectCard sc = (SuspectCard) c;
				if (sc.getSuspect().equals(suggestion.getValue1()))
					return sc;
			} else if (c instanceof WeaponCard) {
				WeaponCard wc = (WeaponCard) c;
				if (wc.getWeapon().equals(suggestion.getValue2()))
					return wc;
			}
		}
		return null;
	}

	@Override
	public void informMove(GameState2 gs, Integer movingPlayerID, Square s) {
		// TODO Auto-generated method stub

	}

	@Override
	public void informSuggestion(GameState2 gs, Integer suggestingPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		/*engine.mustNotExist(new Card[] { new RoomCard(suggestion.getValue0()),
		 	new SuspectCard(suggestion.getValue1()),
		 	new WeaponCard(suggestion.getValue2())
	   		},
		this.IDToOpponentIdx.get(suggestingPlayerID));*/
	}

	@Override
	public void informFailureToRefute(GameState2 gs, Integer failedPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		engine.mustNotExist(new Card[] { new RoomCard(suggestion.getValue0()),
				 new SuspectCard(suggestion.getValue1()),
				 new WeaponCard(suggestion.getValue2())
				   },
		this.IDToOpponentIdx.get(failedPlayerID));
	}

	@Override
	public void informRefutation(GameState2 gs, Integer refutingPlayerID,
			Card refutingCard) {
		engine.mustExist(refutingCard, this.IDToOpponentIdx.get(refutingPlayerID));
	}

	@Override
	public void informRefutationHidden(GameState2 gs, Integer refutingPlayerID,
			Integer suggestingPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		engine.opponentReaction(new Card[] {
				new RoomCard(suggestion.getValue0()),
					new SuspectCard(suggestion.getValue1()),
					new WeaponCard(suggestion.getValue2())
			    },
			    this.IDToOpponentIdx.get(refutingPlayerID),
			    true);
	}

	@Override
	public void gameEnded(GameState2 gs, Integer winnerID,
			Triplet<Room, Suspect, Weapon> winningAccusation) {
		// TODO Auto-generated method stub

	}

	@Override
	public void informFailedAccusation(GameState2 gs, Integer failedPlayerID,
			Triplet<Room, Suspect, Weapon> failedAccusation) {
		// TODO Auto-generated method stub

	}

	@Override
	public Triplet<Room, Suspect, Weapon> accuse(GameState2 gs) {
		for (int r = 0; r < 9; r++) {
			for (int s = 0; s < 6; s++)
				for (int w = 0; w < 6; w++) {
					if (engine.envProb[w][s][r] > 0.95)
						return new Triplet<Room, Suspect, Weapon>(Room.values()[r], 
								Suspect.values()[s], 
								Weapon.values()[w]);
				}
		}
		return null;
	}

}
